import React from 'react';
import {ThemeProps, themeable} from '../../theme';
import {LocaleProps, localeable} from '../../locale';
import {uncontrollable} from 'uncontrollable';
import {Fields, ConditionGroupValue, Funcs} from './types';
import ConditionGroup from './Group';
import defaultConfig from './config';
import {autobind, findTreeIndex, spliceTree, getTree} from '../../utils/helper';
import {findDOMNode} from 'react-dom';
import animtion from '../../utils/Animation';

export interface QueryBuilderProps extends ThemeProps, LocaleProps {
  fields: Fields;
  funcs?: Funcs;
  value?: ConditionGroupValue;
  onChange: (value: ConditionGroupValue) => void;
}

export class QueryBuilder extends React.Component<QueryBuilderProps> {
  config = defaultConfig;

  dragTarget: HTMLElement;
  ghost: HTMLElement;
  host: HTMLElement;
  lastX: number;
  lastY: number;
  lastMoveAt: number = 0;

  @autobind
  handleDragStart(e: React.DragEvent) {
    const target = e.currentTarget;
    const item = target.closest('[data-id]') as HTMLElement;
    this.dragTarget = item;
    this.host = item.closest('[data-group-id]') as HTMLElement;

    const ghost = item.cloneNode(true) as HTMLElement;
    ghost.classList.add('is-ghost');
    this.ghost = ghost;

    e.dataTransfer.setDragImage(item.firstChild as HTMLElement, 0, 0);

    target.addEventListener('dragend', this.handleDragEnd);
    document.body.addEventListener('dragover', this.handleDragOver);
    document.body.addEventListener('drop', this.handleDragDrop);
    this.lastX = e.clientX;
    this.lastY = e.clientY;

    // 应该是 chrome 的一个bug，如果你马上修改，会马上执行 dragend
    setTimeout(() => {
      item.classList.add('is-dragging');
      item.parentElement!.insertBefore(
        item,
        item.parentElement!.firstElementChild
      ); // 挪到第一个，主要是因为样式问题。
    }, 5);
  }

  @autobind
  handleDragOver(e: DragEvent) {
    e.preventDefault();
    const item = (e.target as HTMLElement).closest('[data-id]') as HTMLElement;

    const dx = e.clientX - this.lastX;
    const dy = e.clientY - this.lastY;
    const d = Math.max(Math.abs(dx), Math.abs(dy));
    const now = Date.now();

    // 没移动还是不要处理，免得晃动个不停。
    if (d < 5) {
      if (this.lastMoveAt === 0) {
      } else if (now - this.lastMoveAt > 500) {
        const host = (e.target as HTMLElement).closest(
          '[data-group-id]'
        ) as HTMLElement;

        if (host) {
          this.host = host;
          this.lastMoveAt = now;
          this.lastX = 0;
          this.lastY = 0;
          this.handleDragOver(e);
          return;
        }
      }
      return;
    }

    this.lastMoveAt = now;
    this.lastX = e.clientX;
    this.lastY = e.clientY;

    if (
      !item ||
      item.classList.contains('is-ghost') ||
      item.closest('[data-group-id]') !== this.host
    ) {
      return;
    }

    const container = item.parentElement!;
    const children = [].slice.apply(container!.children);

    const idx = children.indexOf(item);

    if (this.ghost.parentElement !== container) {
      container.appendChild(this.ghost);
    }

    const rect = item.getBoundingClientRect();
    const isAfter = dy > 0 && e.clientY > rect.top + rect.height / 2;
    const gIdx = isAfter ? idx : idx - 1;
    const cgIdx = children.indexOf(this.ghost);

    if (gIdx !== cgIdx) {
      animtion.capture(container);

      if (gIdx === children.length - 1) {
        container.appendChild(this.ghost);
      } else {
        container.insertBefore(this.ghost, children[gIdx + 1]);
      }

      animtion.animateAll();
    }
  }

  @autobind
  handleDragDrop() {
    const onChange = this.props.onChange;
    const fromId = this.dragTarget.getAttribute('data-id')!;
    const toId = this.host.getAttribute('data-group-id')!;
    const children = [].slice.call(this.ghost.parentElement!.children);
    const idx = children.indexOf(this.dragTarget);

    if (~idx) {
      children.splice(idx, 1);
    }

    const toIndex = children.indexOf(this.ghost);
    let value = this.props.value!;

    const indexes = findTreeIndex([value], item => item.id === fromId);

    if (indexes) {
      const origin = getTree([value], indexes.concat())!;
      [value] = spliceTree([value]!, indexes, 1);

      const indexes2 = findTreeIndex([value], item => item.id === toId);

      if (indexes2) {
        [value] = spliceTree([value]!, indexes2.concat(toIndex), 0, origin);
        onChange(value);
      }
    }
  }

  @autobind
  handleDragEnd(e: Event) {
    const target = e.target as HTMLElement;

    target.removeEventListener('dragend', this.handleDragEnd);
    document.body.removeEventListener('dragover', this.handleDragOver);
    document.body.removeEventListener('drop', this.handleDragDrop);

    this.dragTarget.classList.remove('is-dragging');
    delete this.dragTarget;
    this.ghost.parentElement?.removeChild(this.ghost);
    delete this.ghost;
  }

  render() {
    const {classnames: cx, fields, funcs, onChange, value} = this.props;

    return (
      <ConditionGroup
        config={this.config}
        funcs={funcs}
        fields={fields}
        value={value}
        onChange={onChange}
        classnames={cx}
        removeable={false}
        onDragStart={this.handleDragStart}
      />
    );
  }
}

export default themeable(
  localeable(
    uncontrollable(QueryBuilder, {
      value: 'onChange'
    })
  )
);
